package com.github.ghsea.cc.service.analysis;

import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.storm.shade.com.google.common.collect.Lists;
import org.apache.storm.shade.org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.github.ghsea.cc.consts.DurationTypeEnum;
import com.github.ghsea.cc.module.AccessTimes;
import com.github.ghsea.cc.module.InterceptedLog;
import com.github.ghsea.cc.module.Rule;
import com.github.ghsea.cc.service.db.InterceptedAccessService;
import com.github.ghsea.cc.service.db.RuleService;
import com.google.common.base.Optional;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

@Component
public class RuleEngine implements InitializingBean {

	private Logger logger = LoggerFactory.getLogger(RuleEngine.class);

	@Resource
	private RuleService ruleService;

	@Resource
	private InterceptedAccessService accessService;

	@Resource
	private ScheduledExecutorService executor;

	private CopyOnWriteArrayList<Rule> rules;

	private AntPathMatcher pathMather;

	private Date updateTime;

	/**
	 * key=Ip,value=Url
	 * Pattern。此处过期时间不能设置过长，因为storm工程没有提供入口清除黑名单.此入设置黑名单只是为了防止对DB造成压力
	 */
	private Cache<String, String> blackList = CacheBuilder.newBuilder().maximumSize(10000)
			.expireAfterWrite(3, TimeUnit.SECONDS).build();

	public RuleEngine() {
		pathMather = new AntPathMatcher();
		pathMather.setCaseSensitive(false);
	}

	public void afterPropertiesSet() throws Exception {
		List<Rule> initialRules = ruleService.getAllRules();
		if (!CollectionUtils.isEmpty(initialRules)) {
			rules = new CopyOnWriteArrayList<Rule>(ruleService.getAllRules());
		} else {
			rules = new CopyOnWriteArrayList<Rule>();
		}
		updateTime = new Date();

		// 每隔一分钟与DB同步Rule
		executor.scheduleWithFixedDelay(new Runnable() {
			public void run() {
				List<Rule> updatedRules = ruleService.getUpdatedRules(updateTime);
				if (CollectionUtils.isNotEmpty(updatedRules)) {
					final List<Long> deletedRuleIds = Lists.newArrayList();
					List<Rule> newRules = Lists.newArrayList();

					for (Rule updatedRule : updatedRules) {
						if (updatedRule.getIsDeleted() == 1) {
							deletedRuleIds.add(updatedRule.getId());
						} else {
							newRules.add(updatedRule);
						}
					}

					if (!deletedRuleIds.isEmpty()) {
						CollectionUtils.filter(rules, new Predicate<Rule>() {
							public boolean evaluate(Rule object) {
								return deletedRuleIds.contains(object.getId());
							}
						});
					}

					if (!newRules.isEmpty()) {
						for (Rule newRule : newRules) {
							for (int idx = 0; idx < rules.size(); idx++) {
								if (newRule.getId().equals(rules.get(idx).getId())) {
									rules.set(idx, newRule);
									break;
								}
							}
						}
					}
				}
				updateTime = new Date();
			}
		}, 60, 60, TimeUnit.SECONDS);
	}

	/**
	 * 如果access log触发了防CC规则，则写Log(到DB)
	 * 
	 * @return true:触发了防CC规则 ,false:未触发防CC规则
	 */
	public boolean checkAndLog(Rule rule, AccessTimes accessTimes) {
		if (rule == null || blackList.getIfPresent(accessTimes.getIp()) != null) {
			return false;
		}

		Integer durationType = rule.getDurationType();
		long baseInterval = 0L;
		if (durationType == DurationTypeEnum.Minute.getType()) {
			baseInterval = 1 * 1000 * 60;
		} else if (durationType == DurationTypeEnum.Hour.getType()) {
			baseInterval = 1 * 1000 * 60 * 60;
		}

		/**
		 * 时间窗口未过期且访问次数超过了阈值,则将IP打入黑名单并写入数据库的拦截记录
		 */
		long interval = accessTimes.getThisAccessTime().getTime() - accessTimes.getFirstAccessTime().getTime();
		boolean isNotExpried = interval <= baseInterval;
		boolean isAboveThreshold = accessTimes.getTimes().get() >= rule.getMaxTimes();
		if (isNotExpried && isAboveThreshold) {
			blackList.put(accessTimes.getIp(), rule.getUrlPattern());

			InterceptedLog access = new InterceptedLog();
			access.setIp(accessTimes.getIp());
			access.setTimes(accessTimes.getTimes().get());
			access.setRuleId(rule.getId());
			accessService.insertOrUpdateByIp(access);
			return true;
		}

		return false;
	}

	/**
	 * 匹配Rule的url pattern
	 * 
	 * @param requestUrl
	 * @return
	 */
	public Optional<Rule> matchRuleUrl(String host, String requestUrl) {
		for (Rule rule : rules) {
			if (StringUtils.equals(rule.getHost(), host) && pathMather.match(rule.getUrlPattern(), requestUrl)) {
				return Optional.of(rule);
			}
		}

		return Optional.absent();
	}

}
